package com.network.web.Client;

import com.network.web.api.BaiduApi;
import com.network.web.api.ReceiptApi;
import com.network.web.config.BaiduConfig;
import com.network.web.config.BaseConfig;
import com.network.web.config.ReceiptConfig;
import com.network.web.util.FileUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author 28162
 */
@Slf4j
@Component
public class WebClient {
    private final OkHttpClient.Builder okHttpBuilder;
    private final Retrofit.Builder retrofitBuilder;

    public WebClient() {
        okHttpBuilder = new OkHttpClient.Builder();
        retrofitBuilder = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create());
    }

    public void setSslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) {
        this.okHttpBuilder.sslSocketFactory(sslSocketFactory, trustManager);
    }

    public WebResponseBody get(BaiduConfig baiduConfig) {
        BaiduApi baiduApi = buildApi(baiduConfig.getUrl(), baiduConfig.getApiClass());
        Call<ResponseBody> baiduCall = baiduApi.getHomePage();
        return processResponse(baiduCall, baiduConfig.getUrl());
    }

    public WebResponseBody post(ReceiptConfig receiptConfig) {
        ReceiptApi receiptApi = buildApiWithCertificate(
                receiptConfig.getUrl(),
                ReceiptApi.class,
                receiptConfig.getCertificatePath(),
                receiptConfig.getCertificatePassword(),
                receiptConfig.getKeyStoreType(),
                receiptConfig.getSslContextType()
        );
        RequestBody requestBody = RequestBody.create(
                MediaType.get(receiptConfig.getMediaType()),
                receiptConfig.getRequestHeader()
        );

        Call<ResponseBody> receiptCall = receiptApi.requestReceiptInfo(requestBody);
        return processResponse(receiptCall, receiptConfig.getUrl());
    }

    public WebResponseBody processResponse(Call<ResponseBody> call, String targetUrl) {
        WebResponseBody webResponseBody = new WebResponseBody();
        webResponseBody.setUrl(targetUrl);

        try {
            Response<ResponseBody> response = call.execute();
            if (response.isSuccessful()) {
                ResponseBody responseBody = response.body();
                String responseBodyStr = Objects.isNull(responseBody) ? "" : responseBody.string();

                webResponseBody.setCode(String.valueOf(response.code()));
                webResponseBody.setMessage(responseBodyStr);
                log.info("通过get方式请求{}成功, 响应码为: {} , 响应信息为: {}", targetUrl, response.code(), responseBodyStr);
            } else {
                webResponseBody.setCode(String.valueOf(response.code()));
                webResponseBody.setErrorMessage(response.message());
                log.error("通过get方式请求{}失败, 响应码为: {} , 响应信息为: {}", targetUrl, response.code(), response.message());
            }
        } catch (IOException e) {
            webResponseBody.setErrorMessage(e.getMessage());
            webResponseBody.setException(e);
            log.error("执行[通过get方式请求{}]操作失败, 失败原因为: {}", targetUrl, e.getMessage(), e);
        }

        return webResponseBody;
    }

    public <T> T buildApi(String inputUrl, Class<T> serviceClass) {
        return buildApi(inputUrl, serviceClass, null);
    }

    public <T> T buildApiWithCertificate(String inputUrl, Class<T> inputClass, String certificatePath, String certificatePassword, String keyStoreType, String sslContextType) {
        OkHttpClient okHttpClient = buildOkHttpClientWithCertificate(certificatePath, certificatePassword, keyStoreType, sslContextType);

        return buildApi(inputUrl, inputClass, okHttpClient);
    }

    public <T> T buildApi(String inputUrl, Class<T> serviceClass, OkHttpClient okHttpClient) {
        log.info("正在创建访问 {} 的RetrofitClient", inputUrl);
        if (!Objects.isNull(okHttpClient)) {
            retrofitBuilder.client(okHttpClient);
        }
        Retrofit retrofit = retrofitBuilder.baseUrl(inputUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        return retrofit.create(serviceClass);
    }

    public OkHttpClient buildOkHttpClientWithCertificate(String certificatePath, String certificatePassword, String keyStoreType, String sslContextType) {
        log.info("正在根据证书创建访问所需的 OkHttpClient");
        FileInputStream fis = FileUtils.fileToInputStreamByPath(certificatePath);

        try {
            // 加载 PFX 证书
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(fis, certificatePassword.toCharArray());

            // 初始化密钥管理器工厂，用于从密钥库中提取密钥
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, certificatePassword.toCharArray());

            List<X509TrustManager> platformTrustManagers = buildTrustManagerFactory(keyStore);
            X509TrustManager trustManager = buildX509TrustManager(platformTrustManagers);

            // 创建SSL上下文，使用初始化好的密钥管理器和自定义的信任管理器
            SSLContext sslContext = SSLContext.getInstance(sslContextType);
            sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[]{trustManager}, null);

            // 配置OkHttpClient，使用上述SSL上下文创建的SSLSocketFactory
            okHttpBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);

            fis.close();
        } catch (Exception e) {
            log.error("根据证书创建okhttpClient失败原因为: {}", e.getMessage(), e);
            return null;
        }

        return okHttpBuilder.build();
    }

    private static @NotNull List<X509TrustManager> buildTrustManagerFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyStoreException {
        // 初始化信任管理器工厂，用于从密钥库中提取受信任的证书
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);

        // 将平台信任的证书与PFX中的证书合并到自定义的信任管理器中
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        List<X509TrustManager> platformTrustManagers = Arrays.stream(trustManagers)
                .filter(X509TrustManager.class::isInstance)
                .map(X509TrustManager.class::cast)
                .collect(Collectors.toList());
        return platformTrustManagers;
    }

    private static @NotNull X509TrustManager buildX509TrustManager(List<X509TrustManager> platformTrustManagers) {
        return new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                for (X509TrustManager platformTrustManager : platformTrustManagers) {
                    platformTrustManager.checkClientTrusted(chain, authType);
                }
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//                    for (X509TrustManager platformTrustManager : platformTrustManagers) {
//                        platformTrustManager.checkServerTrusted(chain, authType);
//                    }
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                // 返回平台信任的证书
                // 若想返回空证书 return new java.security.cert.X509Certificate[]{};
                return platformTrustManagers.stream()
                        .flatMap(tm -> Arrays.stream(tm.getAcceptedIssuers()))
                        .toArray(X509Certificate[]::new);
            }
        };
    }
}
